package net.daoke.relation.repository;

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import net.daoke.relation.enums.ProtocolEnum;
import net.daoke.relation.model.domain.NetworkRelation;
import org.neo4j.driver.internal.InternalRelationship;
import org.springframework.data.neo4j.core.Neo4jClient;
import org.springframework.data.neo4j.repository.query.Query;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
public class RelationshipRepository {

    private final Neo4jClient neo4jClient;

    public RelationshipRepository(Neo4jClient neo4jClient) {
        this.neo4jClient = neo4jClient;
    }

    /**
     * 检查是否存在指定的网络关系
     *
     * @param sourceNo        源节点编号
     * @param protocol        网络协议类型
     * @param destinationNo   目标节点编号
     * @param destinationPort 目标端口号
     * @return 如果存在指定的网络关系，则返回该关系的对象；如果不存在，则返回null
     */
    public NetworkRelation existsNetworkRelation(String sourceNo, ProtocolEnum protocol, String destinationNo, int destinationPort) {
        Collection<Map<String, Object>> all = neo4jClient.query("MATCH (src:Node {no:$sourceNo})-[r:" + protocol.name() + "]->(dst:Node {no:$destinationNo}) " +
                        "WHERE r.protocol = $protocol AND r.destinationPort = $destinationPort " +
                        "RETURN r.no AS no, src.no AS sourceNo,r.sourcePort AS sourcePort, r.protocol AS protocol, dst.no AS destinationNo,r.destinationPort AS destinationPort,r.createTime AS createTime,r.createTimeStr AS createTimeStr")
                .bind(sourceNo).to("sourceNo")
                .bind(destinationNo).to("destinationNo")
                .bind(protocol.name()).to("protocol")
                .bind(String.valueOf(destinationPort)).to("destinationPort")
                .fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().map(stringObjectMap -> {
                NetworkRelation build = NetworkRelation.builder()
                        .sourceNo(stringObjectMap.get("sourceNo").toString())
                        .sourcePort(Integer.parseInt(stringObjectMap.get("sourcePort").toString()))
                        .protocol(ProtocolEnum.valueOf(stringObjectMap.get("protocol").toString()))
                        .destinationNo(stringObjectMap.get("destinationNo").toString())
                        .destinationPort(Integer.parseInt(stringObjectMap.get("destinationPort").toString()))
                        .build();
                build.setNo(stringObjectMap.get("no").toString());
                build.setCreateTime(Long.parseLong(stringObjectMap.get("createTime").toString()));
                build.setCreateTimeStr(stringObjectMap.get("createTimeStr").toString());
                return build;
            }).toList().get(0);
        } else {
            return null;
        }
    }

    /**
     * 添加网络关系
     *
     * @param networkRelation 要添加的网络关系对象
     * @return 添加后的网络关系对象，如果添加失败则返回null
     */
    public NetworkRelation addNetworkRelation(NetworkRelation networkRelation) {
        Collection<Map<String, Object>> all = neo4jClient.query(
                        "MATCH (src:Node) WHERE src.no = $sourceNo " +
                                "MATCH (dst:Node) WHERE dst.no = $destinationNo " +
                                "CREATE (src)-[r:" + networkRelation.getProtocol().name() +
                                " {no:$no, sourceNo: $sourceNo, destinationNo: $destinationNo, sourcePort: $sourcePort, protocol: $protocol, destinationPort:$destinationPort, createTime:$createTime, createTimeStr:$createTimeStr}]->(dst) " +
                                "RETURN r.no AS no, src.no AS sourceNo,r.sourcePort AS sourcePort, r.protocol AS protocol, dst.no AS destinationNo,r.destinationPort AS destinationPort,r.createTime AS createTime,r.createTimeStr AS createTimeStr")
                .bind(networkRelation.getNo()).to("no")
                .bind(networkRelation.getCreateTime()).to("createTime")
                .bind(networkRelation.getCreateTimeStr()).to("createTimeStr")
                .bind(networkRelation.getSourceNo()).to("sourceNo")
                .bind(String.valueOf(networkRelation.getSourcePort())).to("sourcePort")
                .bind(networkRelation.getDestinationNo()).to("destinationNo")
                .bind(networkRelation.getProtocol().name()).to("protocol")
                .bind(String.valueOf(networkRelation.getDestinationPort())).to("destinationPort").fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().map(stringObjectMap -> {
                NetworkRelation build = NetworkRelation.builder()
                        .sourceNo(stringObjectMap.get("sourceNo").toString())
                        .sourcePort(Integer.parseInt(stringObjectMap.get("sourcePort").toString()))
                        .protocol(ProtocolEnum.valueOf(stringObjectMap.get("protocol").toString()))
                        .destinationNo(stringObjectMap.get("destinationNo").toString())
                        .destinationPort(Integer.parseInt(stringObjectMap.get("destinationPort").toString()))
                        .build();
                build.setNo(stringObjectMap.get("no").toString());
                build.setCreateTime(Long.parseLong(stringObjectMap.get("createTime").toString()));
                build.setCreateTimeStr(stringObjectMap.get("createTimeStr").toString());
                return build;
            }).toList().get(0);
        } else {
            return null;
        }
    }

    /**
     * 查询无序路径（源 IP -> 目标 IP，必须经过指定的节点，但顺序无关）
     *
     * @param sourceIp      源 IP 地址
     * @param destinationIp 目标 IP 地址
     * @param middleNodeIds 必须经过的节点 ID 列表
     * @return 返回第一条满足条件的路径信息，如果没有找到路径则返回 null
     */
    Map<String, Object> findPathUnordered(String sourceIp, String destinationIp, List<Long> middleNodeIds) {
        Collection<Map<String, Object>> all = neo4jClient.query("MATCH path=(src:Host)-[:CONNECTED_TO*]->(dst:Host) " +
                "WHERE $sourceIp IN src.ips AND $destinationIp IN dst.ips " +
                "AND ALL(node IN nodes(path)[1..-1] WHERE id(node) IN $middleNodeIds) " +
                "RETURN path").fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().findFirst().orElse(null);
        } else {
            return null;
        }
    }

    // 查询有序路径（源 IP -> 目标 IP，路径必须严格按照 `middleNodeIds` 顺序）
    @Query()
    Map<String, Object> findPathOrdered(String sourceIp, String destinationIp, List<Long> middleNodeIds) {
        Collection<Map<String, Object>> all = neo4jClient.query("MATCH path=(src:Host)-[:CONNECTED_TO*]->(dst:Host) " +
                "WHERE $sourceIp IN src.ips AND $destinationIp IN dst.ips " +
                "AND [node IN nodes(path)[1..-1] | id(node)] = $middleNodeIds " +
                "RETURN path").fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().findFirst().orElse(null);
        } else {
            return null;
        }
    }

    /**
     * 根据节点编号获取网络关系列表
     *
     * @param nodeNo 节点编号
     * @return 网络关系列表，如果未找到则返回null
     */
    public List<NetworkRelation> getNetworkRelationsByNodeNo(String nodeNo) {
        Collection<Map<String, Object>> all = neo4jClient.query("MATCH (src)-[r]-(dst) WHERE dst.no = $nodeNo return DISTINCT r")
                .bind(nodeNo).to("nodeNo").fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().map(stringObjectMap -> {
                InternalRelationship relationship = (InternalRelationship) stringObjectMap.get("r");
                NetworkRelation build = NetworkRelation.builder()
                        .sourceNo(relationship.asMap().get("sourceNo").toString())
                        .sourcePort(Integer.parseInt(relationship.asMap().get("sourcePort").toString()))
                        .protocol(ProtocolEnum.valueOf(relationship.asMap().get("protocol").toString()))
                        .destinationNo(relationship.asMap().get("destinationNo").toString())
                        .destinationPort(Integer.parseInt(relationship.asMap().get("destinationPort").toString()))
                        .build();
                build.setNo(relationship.asMap().get("no").toString());
                build.setCreateTime(Long.parseLong(relationship.asMap().get("createTime").toString()));
                build.setCreateTimeStr(relationship.asMap().get("createTimeStr").toString());
                return build;
            }).toList();
        }
        return null;
    }

    /**
     * 更新网络关系
     *
     * 通过指定源节点编号、旧目标节点编号和新目标节点编号，来更新网络关系。
     * 首先，通过查询找到旧的关系和属性，然后删除旧关系，并根据传递的上下文创建新的关系。
     *
     * @param sourceNo        源节点编号
     * @param oldTargetNo     旧目标节点编号
     * @param newTargetNo     新目标节点编号
     * @return 更新后的网络关系对象，如果未找到则返回null
     */
    public NetworkRelation updateNetworkRelation(String sourceNo, String oldTargetNo, String newTargetNo) {
        Collection<Map<String, Object>> all = neo4jClient.query(
                                // 1. 查找旧关系和属性
                                "MATCH (source {no: $sourceNo})-[r]->(oldTarget {no: $oldTargetNo}) " +
                                "WITH r, properties(r) AS relProps, type(r) AS rType " +
                                // 2. 删除旧关系
                                "DELETE r " +
                                // 3. 传递上下文并创建新关系
                                "WITH relProps, rType " +
                                "MATCH (source {no: $sourceNo}), (newTarget {no: $newTargetNo}) " +
//                                "CREATE (source)-[r_new:rType]->(newTarget) " +
//                                "SET r_new = relProps " +
                                "CALL apoc.create.relationship(source, rType, relProps, newTarget) YIELD rel "+
                                "RETURN rel")
                .bind(sourceNo).to("sourceNo")
                .bind(oldTargetNo).to("oldTargetNo")
                .bind(newTargetNo).to("newTargetNo")
                .fetch().all();
        if (CollectionUtil.isNotEmpty(all)) {
            return all.stream().map(stringObjectMap -> {
                InternalRelationship relationship = (InternalRelationship) stringObjectMap.get("rel");
                NetworkRelation build = NetworkRelation.builder()
                        .sourceNo(relationship.asMap().get("sourceNo").toString())
                        .sourcePort(Integer.parseInt(relationship.asMap().get("sourcePort").toString()))
                        .protocol(ProtocolEnum.valueOf(relationship.asMap().get("protocol").toString()))
                        .destinationNo(relationship.asMap().get("destinationNo").toString())
                        .destinationPort(Integer.parseInt(relationship.asMap().get("destinationPort").toString()))
                        .build();
                build.setNo(relationship.asMap().get("no").toString());
                build.setCreateTime(Long.parseLong(relationship.asMap().get("createTime").toString()));
                build.setCreateTimeStr(relationship.asMap().get("createTimeStr").toString());
                return build;
            }).toList().getFirst();
        }
        return null;
    }
}
